/**
 * 
 */
package com.arsl.client.view;

import java.util.ArrayList;
import java.util.Date;

import org.vaadin.gwtgraphics.client.DrawingArea;
import org.vaadin.gwtgraphics.client.Image;
import org.vaadin.gwtgraphics.client.animation.Animate;
import org.vaadin.gwtgraphics.client.shape.Rectangle;

import com.arsl.client.GlobalInfo;
import com.arsl.client.data.Content;
import com.arsl.client.data.Paper;
import com.arsl.client.data.Tag;
import com.arsl.client.event.ShowViewEvent;
import com.arsl.client.presenter.PageTaggerPresenter;
import com.arsl.client.util.Constants;
import com.arsl.client.util.Utils;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.MouseDownEvent;
import com.google.gwt.event.dom.client.MouseDownHandler;
import com.google.gwt.event.dom.client.MouseMoveEvent;
import com.google.gwt.event.dom.client.MouseMoveHandler;
import com.google.gwt.event.dom.client.MouseUpEvent;
import com.google.gwt.event.dom.client.MouseUpHandler;
import com.google.gwt.i18n.client.HasDirection.Direction;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.AbsolutePanel;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FileUpload;
import com.google.gwt.user.client.ui.FormPanel;
import com.google.gwt.user.client.ui.FormPanel.SubmitCompleteEvent;
import com.google.gwt.user.client.ui.FormPanel.SubmitCompleteHandler;
import com.google.gwt.user.client.ui.HasHorizontalAlignment.HorizontalAlignmentConstant;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;

/**
 * @author NuwanSam
 * 
 */
public class PageTagger extends Composite {

	protected static final double WIDTH_MIN = 10;
	protected static final double HEIGHT_MIN = 10;
	protected static final int ANIMATE_SPEED = 2;
	protected static final int TAG_ZINDEX = 2;
	protected static final int IMG_ZINDEX = -1;

	private static PageTaggerUiBinder uiBinder = GWT
			.create(PageTaggerUiBinder.class);

	int arPageId;
	int paperId;
	int resizedHeight;
	int resizedWidth;
	ContentAccordion accord;
	DrawingArea pageCanvas;
	Image imgPage;
	PopupPanel popup;
	PageTaggerPresenter presenter;
	Paper paper;

	protected int dragStartX;

	protected int dragStartY;

	protected TagWidget drawingRect;

	protected double cWidth;

	protected double cHeight;

	protected boolean isDrawing;

	protected ArrayList<TagWidget> tagWidgets;
	protected boolean isMoving;
	protected TagWidget movingTag;
	protected int movingX;
	protected int movingY;
	protected int moveStartTagX;
	protected int moveStartTagY;
	protected boolean isResizing;
	protected int resizingDir;
	protected TagWidget resizingTag;
	protected int resizingX;
	protected int resizingY;
	protected int resizingStartX;
	protected int resizingStartY;
	protected int resizingStartH;
	protected int resizingStartW;
	protected TagWidget currentTag;

	/**
	 * @return the paper
	 */
	public Paper getPaper() {
		return paper;
	}

	/**
	 * @param paper
	 *            the paper to set
	 */
	public void setPaper(Paper paper) {
		this.paper = paper;
	}

	public void setCanvas() {
		if (pageCanvas == null) {
			pageCanvas = new DrawingArea(resizedWidth, resizedHeight);
			pageCanvas.getElement().getStyle().setBackgroundColor("grey");

			// pageCanvas.addClickHandler(canvasClick);
			pageCanvas.addMouseDownHandler(new MouseDownHandler() {

				@Override
				public void onMouseDown(MouseDownEvent event) {
					popup.clear();
					popup.hide();
					dragStartX = event.getX();
					dragStartY = event.getY();
					drawingRect = new TagWidget(dragStartX, dragStartY, 1, 1);
					drawingRect.getElement().getStyle().setZIndex(TAG_ZINDEX);
					cWidth = 1;
					cHeight = 1;
					TagWidget cTag = overlappingTag(drawingRect);
					if (cTag != null) {
						resizingDir = cTag.resizingDirection(event.getX(),
								event.getY());
						if (resizingDir != cTag.NO_RESIZE) {
							// is a resize
							isResizing = true;
							resizingTag = cTag;
							resizingX = dragStartX;
							resizingY = dragStartY;
							resizingStartX = cTag.getX();
							resizingStartY = cTag.getY();
							resizingStartH = cTag.getHeight();
							resizingStartW = cTag.getWidth();
						} else {

							// is a move
							isMoving = true;
							movingTag = cTag;
							movingX = dragStartX;
							movingY = dragStartY;
							moveStartTagX = cTag.getX();
							moveStartTagY = cTag.getY();
						}
					} else {
						// is a draw
						isDrawing = true;
						drawingRect.setPageTagger(PageTagger.this);
						pageCanvas.add(drawingRect);
					}
				}
			});

			pageCanvas.addMouseMoveHandler(new MouseMoveHandler() {

				@Override
				public void onMouseMove(MouseMoveEvent event) {
					cWidth = Math.abs(event.getX() - dragStartX);
					cHeight = Math.abs(event.getY() - dragStartY);
					if (isDrawing) {
						new Animate(drawingRect, "width", cWidth, Math
								.abs(event.getX() - dragStartX), 10).start();

						new Animate(drawingRect, "height", cHeight, Math
								.abs(event.getY() - dragStartY), 10).start();
					} else if (isMoving) {
						int cx = movingTag.getX();
						int cy = movingTag.getY();
						new Animate(movingTag, "x", cx, cx + event.getX()
								- movingX, 10).start();
						new Animate(movingTag, "y", cy, cy + event.getY()
								- movingY, 10).start();
						movingX = event.getX();
						movingY = event.getY();
					} else if (isResizing) {
						int cx = resizingTag.getX();
						int cy = resizingTag.getY();
						int ch = resizingTag.getHeight();
						int cw = resizingTag.getWidth();
						if (resizingDir == TagWidget.RESIZE_TO_NW) {
							new Animate(resizingTag, "x", cx, event.getX(),
									ANIMATE_SPEED).start();
							new Animate(resizingTag, "y", cy, event.getY(),
									ANIMATE_SPEED).start();
							new Animate(resizingTag, "width", cw, cw + cx
									- event.getX(), ANIMATE_SPEED).start();
							new Animate(resizingTag, "height", ch, ch + cy
									- event.getY(), ANIMATE_SPEED).start();
						} else if (resizingDir == TagWidget.RESIZE_TO_NE) {
							new Animate(resizingTag, "width", cw, cw
									+ event.getX() - resizingX, ANIMATE_SPEED)
									.start();
							new Animate(resizingTag, "height", ch, ch
									+ resizingY - event.getY(), ANIMATE_SPEED)
									.start();
							new Animate(resizingTag, "y", cy, event.getY(),
									ANIMATE_SPEED).start();
						} else if (resizingDir == TagWidget.RESIZE_TO_SE) {
							new Animate(resizingTag, "width", cw, cw
									+ event.getX() - resizingX, ANIMATE_SPEED)
									.start();
							new Animate(resizingTag, "height", ch, ch
									- resizingY + event.getY(), ANIMATE_SPEED)
									.start();
						} else if (resizingDir == TagWidget.RESIZE_TO_SW) {
							new Animate(resizingTag, "width", cw, cw
									- event.getX() + resizingX, ANIMATE_SPEED)
									.start();
							new Animate(resizingTag, "height", ch, ch
									- resizingY + event.getY(), ANIMATE_SPEED)
									.start();
							new Animate(resizingTag, "x", cx, event.getX(),
									ANIMATE_SPEED).start();
						}
						resizingX = event.getX();
						resizingY = event.getY();

					}
				}
			});

			pageCanvas.addMouseUpHandler(new MouseUpHandler() {

				@Override
				public void onMouseUp(MouseUpEvent event) {
					FloatRectangle fRect;
					if (cWidth < WIDTH_MIN || cHeight < HEIGHT_MIN) {
						pageCanvas.remove(drawingRect);
						canvasClick(event);
						isDrawing = false;
						isMoving = false;
						isResizing = false;
						return;
					}
					if (isDrawing) {
						isDrawing = false;
						if (overlappingTag(drawingRect) != null) {
							Window.alert("Cannot add the tag since it overlaps with existing tags");
							pageCanvas.remove(drawingRect);
						} else {
							tagWidgets.add(drawingRect);
							fRect = getTagRectangle(drawingRect);
							GlobalInfo.rpcService.addTag(getArPageId(),
									fRect.getX(), fRect.getY(),
									fRect.getHeight(), fRect.getWidth(),
									new AsyncCallback<Integer>() {

										@Override
										public void onSuccess(Integer result) {
											drawingRect.setTagId(result);
										}

										@Override
										public void onFailure(Throwable caught) {
											Window.alert(caught.getMessage());

										}
									});
							drawingRect.setControls();

						}
					} else if (isMoving) {
						isMoving = false;
						TagWidget overlapTag = overlappingTag(movingTag);
						if (overlapTag != null && overlapTag != movingTag) {
							Window.alert("You cannot move to the desired location since it overlaps with another tag!");
							movingTag.setX(moveStartTagX);
							movingTag.setY(moveStartTagY);
							return;
						}
						fRect = getTagRectangle(movingTag);
						GlobalInfo.rpcService.changeTagPlacement(
								movingTag.getTagId(), fRect.getX(),
								fRect.getY(), fRect.getWidth(),
								fRect.getWidth(), new AsyncCallback<Boolean>() {

									@Override
									public void onSuccess(Boolean result) {
										// TODO Auto-generated method stub

									}

									@Override
									public void onFailure(Throwable caught) {
										Window.alert(caught.getMessage());

									}
								});

					} else if (isResizing) {
						isResizing = false;
						TagWidget overlapTag = overlappingTag(resizingTag);
						if (overlapTag != null && overlapTag != resizingTag) {
							Window.alert("You cannot resize to the desired size since it overlaps with another tag!");
							resizingTag.setX(resizingStartX);
							resizingTag.setY(resizingStartY);
							resizingTag.setHeight(resizingStartH);
							resizingTag.setWidth(resizingStartW);
							return;
						}
						fRect = getTagRectangle(resizingTag);
						GlobalInfo.rpcService.changeTagPlacement(
								resizingTag.getTagId(), fRect.getX(),
								fRect.getY(), fRect.getWidth(),
								fRect.getWidth(), new AsyncCallback<Boolean>() {

									@Override
									public void onSuccess(Boolean result) {
										// TODO Auto-generated method stub

									}

									@Override
									public void onFailure(Throwable caught) {
										Window.alert(caught.getMessage());

									}
								});
					}
				}
			});

			absPanel.add(pageCanvas);
		}
	}

	protected FloatRectangle getTagRectangle(TagWidget drawingRect) {
		FloatRectangle rect = new FloatRectangle();
		rect.setX(drawingRect.getX() * paper.getWidth() / getResizedWidth());
		rect.setY(drawingRect.getY() * paper.getHeight() / getResizedHeight());
		rect.setWidth(drawingRect.getWidth() * paper.getWidth()
				/ getResizedWidth());
		rect.setHeight(drawingRect.getHeight() * paper.getHeight()
				/ getResizedHeight());
		return rect;
	}

	protected TagWidget overlappingTag(TagWidget tagWidget) {
		for (TagWidget tw : tagWidgets) {
			if (tw.overlaps(tagWidget) && tw != tagWidget) {
				return tw;
			}
		}
		return null;
	}

	protected void canvasClick(MouseUpEvent event) {
		TagWidget handlerTag = null;
		if (tagWidgets != null) {
			for (TagWidget tag : tagWidgets) {
				if (tag.contains(event.getX(), event.getY())) {
					handlerTag = tag;
					break;
				}
			}
			if (handlerTag != null)
				handlerTag.clickHandlder(event.getX(), event.getY());
		}
	}

	public DrawingArea getCanvas() {
		return pageCanvas;
	}

	public float getResizedHeight() {
		return this.resizedHeight;
	}

	public void setResizedHeight(float height) {
		this.resizedHeight = (int) Math.round(height);
	}

	public float getResizedWidth() {
		return this.resizedWidth;
	}

	public void setResizedWidth(float width) {
		this.resizedWidth = (int) Math.round(width);
	}

	public int getArPageId() {
		return arPageId;
	}

	public void setArPageId(int arPageId) {
		this.arPageId = arPageId;
	}

	public int getPaperId() {
		return paperId;
	}

	public void setPaperId(int paperId) {
		this.paperId = paperId;
	}

	/*
	 * public com.google.gwt.user.client.ui.Image getPageImage() { return
	 * imgPage; }
	 */

	interface PageTaggerUiBinder extends UiBinder<Widget, PageTagger> {
	}

	/**
	 * Because this class has a default constructor, it can be used as a binder
	 * template. In other words, it can be used in other *.ui.xml files as
	 * follows: <ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
	 * xmlns:g="urn:import:**user's package**">
	 * <g:**UserClassName**>Hello!</g:**UserClassName> </ui:UiBinder> Note that
	 * depending on the widget that is used, it may be necessary to implement
	 * HasHTML instead of HasText.
	 */

	public PageTagger() {
		initWidget(uiBinder.createAndBindUi(this));
		uploadForm.setMethod(FormPanel.METHOD_POST);
		// The HTTP request is encoded in multipart format.
		uploadForm.setEncoding(FormPanel.ENCODING_MULTIPART); // multipart MIME
		// encoding
		uploadForm.setWidget(fuPageImage);

		uploadForm.addSubmitCompleteHandler(new SubmitCompleteHandler() {

			@Override
			public void onSubmitComplete(SubmitCompleteEvent event) {
				String str = event.getResults();
				HTML html = new HTML(str);
				String url = html.getText().substring(1);
				url = url.substring(0, url.length() - 1);
				setImage(url);
			}
		});

		tagWidgets = new ArrayList<TagWidget>();
		popup = new PopupPanel();
	}

	@UiField
	Button doneBtn;

	@UiField
	FileUpload fuPageImage;

	@UiField
	FormPanel uploadForm;

	@UiField
	AbsolutePanel absPanel;

	@UiHandler("fuPageImage")
	void onFileUpload(ChangeEvent e) {
		uploadForm.setAction("/paperazi/httpservices?hdnARPageId=" + arPageId);
		uploadForm.submit();
		tryLoadExistingImage();
	}

	@UiHandler("doneBtn")
	void onDone(ClickEvent e) {
		ShowViewEvent ev = new ShowViewEvent();
		ev.setArg(ShowViewEvent.VIEW_ARG, ShowViewEvent.VIEW_CREATE_AR_PAGE);

		GlobalInfo.eventBus.fireEvent(ev);
	}

	public void setPresenter(PageTaggerPresenter pageTaggerPresenter) {
		this.presenter = pageTaggerPresenter;
	}

	public void tryLoadExistingImage() {
		presenter.getPageImageUrIfExists(arPageId);
		// loadTags();
	}

	public void getTags() {
		presenter.getPaperTags(arPageId);
	}

	public void drawTags(Tag[] tags) {
		TagWidget widget;
		for (Tag tag : tags) {
			widget = new TagWidget(tag.getStartx(), tag.getStarty(),
					tag.getWidth(), tag.getHeight());
			widget.setTagId(tag.getId());
			widget.getElement().getStyle().setZIndex(TAG_ZINDEX);

			widget.setControls();
			widget.setPageTagger(PageTagger.this);

			pageCanvas.add(widget);
			tagWidgets.add(widget);

		}
	}

	public void getPaperDetails() {
		presenter.getPaperDetails(paperId);
	}

	public Boolean setImage(String imageUrl) {
		if (Utils.isNullOrEmpty(imageUrl)) {

			return false;
		}

		if (imgPage != null) {
			pageCanvas.remove(imgPage);
		}

		imageUrl = imageUrl + "?lastmod="
				+ String.valueOf(new Date().getTime());

		imgPage = new Image(0, 0, resizedWidth, resizedHeight, imageUrl);

		// imgPage.setPixelSize();
		imgPage.setWidth(String.valueOf(resizedWidth) + "px");
		imgPage.setHeight(String.valueOf(resizedHeight) + "px");
		imgPage.getElement().getStyle().setZIndex(IMG_ZINDEX);

		// pageCanvas.remove(imgPage);
		pageCanvas.add(imgPage);
		for (TagWidget widget : tagWidgets) {
			pageCanvas.remove(widget);
			pageCanvas.add(widget);
		}
		return true;
	}

	/*
	 * Handlers
	 */

	/*
	 * Private Methods
	 */
	public void loadTagContent(Content[] result) {

		// Add 3 ContentInput widgets. Needs to get it dynamically.
		Integer popWidth = Constants.POPUP_PANEL_WIDTH;
		Integer popHeight = Constants.POPUP_PANEL_HEIGHT;

		if (result != null && result.length > 0) {
			accord = new ContentAccordion(Unit.PX);
			accord.addContent(result);
		} else {
			// Load empty content
			accord = new ContentAccordion();
		}

		Button submitButton = Utils.getButton("Submit", submitContentHandler,
				"100px", "30px", "g-button-medium", "g-button-submit", null);
		Button addButton = Utils.getButton("Add New", addContentHandler,
				"70px", "30px", "g-button-small", "g-button-submit", null);

		accord.setWidth(String.valueOf(popWidth) + "px");
		accord.setHeight(String.valueOf(popHeight) + "px");

		VerticalPanel mainPanel = new VerticalPanel();
		mainPanel.setHorizontalAlignment(HorizontalAlignmentConstant
				.endOf(Direction.LTR));
		HorizontalPanel buttonPanel = new HorizontalPanel();

		buttonPanel.add(submitButton.asWidget());
		buttonPanel.add(addButton.asWidget());
		buttonPanel.setHorizontalAlignment(HorizontalAlignmentConstant
				.endOf(Direction.LTR));
		mainPanel.add(accord.asWidget());
		mainPanel.add(buttonPanel);
		popup.clear();

		popup.add(mainPanel);
		popup.center();
		popup.show();
	}

	public void removeTag(TagWidget tagWidget) {
		pageCanvas.remove(tagWidget);
		tagWidgets.remove(tagWidget);
		GlobalInfo.rpcService.removeTag(tagWidget.getTagId(),
				new AsyncCallback<Boolean>() {

					@Override
					public void onFailure(Throwable caught) {
						Window.alert(caught.getMessage());

					}

					@Override
					public void onSuccess(Boolean result) {
						// TODO Auto-generated method stub

					}
				});
	}

	public void showContentEditPanel(TagWidget tagWidget) {
		currentTag = tagWidget;
		presenter.getTagContent(tagWidget.getTagId());

	}

	ClickHandler submitContentHandler = new ClickHandler() {

		@Override
		public void onClick(ClickEvent event) {

			Content[] contents = new Content[accord.contentList.size()];
			Integer i = 0;

			for (ContentInputWidget widget : accord.contentList.values()) {
				contents[i] = accord.GetInnerContent(widget);
				i = i + 1;
			}

			presenter.updateTagContent(currentTag.getTagId(), contents);

			// Perform the action

		}
	};
	ClickHandler addContentHandler = new ClickHandler() {

		@Override
		public void onClick(ClickEvent event) {
			try {
				accord.addNewWidget();
			} catch (Exception e) {
				Window.alert(e.getMessage());
			}
		}
	};

	public void loadTags() {
		GlobalInfo.rpcService.getTags(arPageId, new AsyncCallback<Tag[]>() {

			@Override
			public void onSuccess(Tag[] tags) {
				TagWidget widget;
				for (Tag tag : tags) {
					widget = new TagWidget(tag.getStartx() * getResizedWidth()
							/ paper.getWidth(), tag.getStarty()
							* getResizedHeight() / paper.getHeight(), tag
							.getWidth() * getResizedWidth() / paper.getWidth(),
							tag.getHeight() * getResizedHeight()
									/ paper.getHeight());

					widget.getElement().getStyle().setZIndex(TAG_ZINDEX);
					widget.setTagId(tag.getId());
					widget.setControls();
					widget.setPageTagger(PageTagger.this);
					pageCanvas.add(widget);
					tagWidgets.add(widget);
				}
			}

			@Override
			public void onFailure(Throwable caught) {
				Window.alert(caught.getMessage());

			}
		});
	}

	public void submitCompleted() {
		Window.alert("Content updated successfully");
		popup.hide();
	}

	private class FloatRectangle {
		float x;
		float y;
		float height;
		float width;

		/**
		 * @return the x
		 */
		public float getX() {
			return x;
		}

		/**
		 * @param x
		 *            the x to set
		 */
		public void setX(float x) {
			this.x = x;
		}

		/**
		 * @return the y
		 */
		public float getY() {
			return y;
		}

		/**
		 * @param y
		 *            the y to set
		 */
		public void setY(float y) {
			this.y = y;
		}

		/**
		 * @return the height
		 */
		public float getHeight() {
			return height;
		}

		/**
		 * @param height
		 *            the height to set
		 */
		public void setHeight(float height) {
			this.height = height;
		}

		/**
		 * @return the width
		 */
		public float getWidth() {
			return width;
		}

		/**
		 * @param width
		 *            the width to set
		 */
		public void setWidth(float width) {
			this.width = width;
		}

	}
}
